
#pragma once

#include "dmString.h"
#include <fstream>

namespace DM {

	// LogMessageLevel + LoggingLevel > DM_LOG_THRESHOLD = message logged
    #define DM_LOG_THRESHOLD 4

    /** The level of detail to which the log will go into.
    */
    enum LoggingLevel
    {
        LL_LOW = 1,
        LL_NORMAL = 2,
        LL_BOREME = 3
    };

    /** The importance of a logged message.
    */
    enum LogMessageLevel
    {
        LML_TRIVIAL = 1,
        LML_NORMAL = 2,
        LML_CRITICAL = 3
    };

    /** @remarks Pure Abstract class, derive this class and register to the Log to listen to log messages */
    class LogListener
    {
	public:
        virtual ~LogListener() {}
        virtual void messageLogged( const String& message, LogMessageLevel lml, bool maskDebug, const String &logName ) = 0;
    };


    /**
    @remarks
         Log class for writing debug/log data to files.
    @note
        <br>Should not be used directly, but trough the LogManager class.
    */
	class Log
    {
    protected:
        std::ofstream	mfpLog;
        LoggingLevel	mLogLevel;
        bool			mDebugOut;
        bool			mSuppressFile;
        bool			mTimeStamp;
        String			mLogName;

		typedef std::vector<LogListener*> mtLogListener;
        mtLogListener mListeners;

    public:

		class Stream;

        /**
        @remarks
            Usual constructor
        */
		Log();
        Log( const String& name, bool debugOutput = false, bool suppressFileOutput = false);

        /**
        @remarks
        Default destructor.
        */
        ~Log();

		/// Return the name of the log
		const String& getName() const { return mLogName; }
		/// Get whether debug output is enabled for this log
		bool isDebugOutputEnabled() const { return mDebugOut; }
		/// Get whether file output is suppressed for this log
		bool isFileOutputSuppressed() const { return mSuppressFile; }
 		/// Get whether time stamps are printed for this log
		bool isTimeStampEnabled() const { return mTimeStamp; }

		void setName(const String& name);
        /** Log a message to the debugger and to log file (the default is
            "<code>DM.log</code>"),
        */
        void logMessage( const String& message, LogMessageLevel lml = LML_NORMAL, bool maskDebug = false );

		/** Get a stream object targetting this log. */
		Stream stream(LogMessageLevel lml = LML_NORMAL, bool maskDebug = false);

        /**
        @remarks
            Enable or disable outputting log messages to the debugger.
        */
        void setDebugOutputEnabled(bool debugOutput);
		/**
        @remarks
            Sets the level of the log detail.
        */
        void setLogDetail(LoggingLevel ll);
        /**
        @remarks
            Enable or disable time stamps.
        */
        void setTimeStampEnabled(bool timeStamp);
		/** Gets the level of the log detail.
		*/
		LoggingLevel getLogDetail() const { return mLogLevel; }
        /**
        @remarks
            Register a listener to this log
        @param
            A valid listener derived class
        */
        void addListener(LogListener* listener);

        /**
        @remarks
            Unregister a listener from this log
        @param
            A valid listener derived class
        */
        void removeListener(LogListener* listener);

		/** Stream object which targets a log.
		*/
		class Stream
		{
		public:
			typedef StringUtil::StrStreamType BaseStream;

		protected:
			Log* mTarget;
			LogMessageLevel mLevel;
			bool mMaskDebug;
			BaseStream mCache;

		public:

			/// Simple type to indicate a flush of the stream to the log
			struct Flush {};

			Stream(Log* target, LogMessageLevel lml, bool maskDebug)
				:mTarget(target), mLevel(lml), mMaskDebug(maskDebug)
			{

			}
			// copy constructor
			Stream(const Stream& rhs) 
				: mTarget(rhs.mTarget), mLevel(rhs.mLevel),	mMaskDebug(rhs.mMaskDebug)
			{
				// explicit copy of stream required, gcc doesn't like implicit
				mCache.str(rhs.mCache.str());
			} 
			~Stream()
			{
				// flush on destroy
				if (mCache.tellp() > 0)
				{
					mTarget->logMessage(mCache.str(), mLevel, mMaskDebug);
				}
			}

			template <typename T>
			Stream& operator<< (const T& v)
			{
				mCache << v;
				return *this;
			}

			Stream& operator<< (const Flush& v)
			{
                                (void)v;
				mTarget->logMessage(mCache.str(), mLevel, mMaskDebug);
				mCache.str(StringUtil::BLANK);
				return *this;
			}

			BaseStream& Cache(){return mCache;};

		};
    };
}

#define dmAcceptLogMacro() \
	public:             \
    DM::Log mLogUtility;    

#define dmSetLogFileNameMacro(fileName)       \
{                                             \
     mLogUtility.setName(fileName);           \
}                                       

#define dmLogMacro(x) \
	dmLogWithObjectMacro(this,x)

#define dmLogWithObjectMacro(self,x)                    \
{                                                       \
	DM::Log::Stream stream = self->mLogUtility.stream();\
	DM::Log::Stream::BaseStream& cashe = stream.Cache();\
	cashe << x;                                         \
}